
class MyBlockingQueue {
    // 使用一个 String 类型的数组来保存元素. 假设这里只存 String
    private String[] items = new String[1024];

    // 指向队列的头部
    volatile private int head = 0;

    // 指向队列的尾部的下一个元素. 总的来说, 队列中有效元素的范围 [head, tail)
    // 当 head 和 tail 相等(重合), 相当于空的队列
    // 当 head 和 tail 相等(重合), 也有可能是队列为满的情况
    volatile private int tail = 0;

    // 使用 size 来表示元素个数
    volatile private int size = 0;

    private Object locker = new Object();

    public void put(String elem) throws InterruptedException {
        // 此处的写法就相当于直接把 synchronized 写到方法上了
            synchronized (locker) {
                while (size >= items.length) {
                    // 队列满了.
                    locker.wait();

                    //如果我这里的wait使用的是trycatch的方式来写
                    //一旦是interrupt唤醒，此时代码往下走进入catch，catch执行完毕，方法不会结束，继续往下执行，也就触发了“覆盖元素”逻辑
                    //但是这个时候因为不是notify唤醒的,所以size依旧>=items.length,这个时候执行下面的逻辑就会出现问题
                    //所以wait醒了之后，再判定一次条件
                    //如果条件还是为队列满，继续wait.如果条件队列不满，就可以继续执行了
                    //此处的while目的不是为了循环
                    //而是借助循环的方式，巧妙的实现wait醒了之后，再次确认一下条件
                }

                items[tail] = elem;
                ++tail;
                if (tail >= items.length) {
                    tail = 0;
                }
                ++size;
                // 用来唤醒队列为空的阻塞情况
                locker.notify();
            }
    }

    public String take() throws InterruptedException {
        synchronized (locker) {
            while (size == 0) {
                // 队列为空, 暂时不能出队列.
                locker.wait();
            }

            String elem = items[head];
            ++head;

            //因为有size的阻拦,所以不可能出现head > tail的情况
            if (head >= items.length) {
                head = 0;
            }
            --size;
            // 使用这个 notify 来唤醒队列满的阻塞情况
            locker.notify();
            return elem;
        }
    }
}

public class Demo21 {
    public static void main(String[] args) throws InterruptedException {
//        MyBlockingQueue queue = new MyBlockingQueue();
//        queue.put("aaa");
//        queue.put("bbb");
//        queue.put("ccc");
//
//        String elem = queue.take();
//        System.out.println("elem=" + elem);
//        elem = queue.take();
//        System.out.println("elem=" + elem);
//        elem = queue.take();
//        System.out.println("elem=" + elem);
//        elem = queue.take();
//        System.out.println("elem=" + elem);

        MyBlockingQueue myBlockingQueue = new MyBlockingQueue();

        // 创建两个线程, 表示生产者和消费者
        Thread thread1 = new Thread(() -> {
                int count = 0;
                while (true) {
                    try {
                        myBlockingQueue.put(String.valueOf(count));
                        System.out.println("生产元素: " + count);
                        ++count;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        });

        Thread thread2 = new Thread(() -> {
            while (true) {
                try {
                    String elem = myBlockingQueue.take();
                    Thread.sleep(1000);
                    System.out.println("消费元素: " + elem);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}
